namespace ScrumSprintMonitor.Testbench.Impl
{
	#region #using Directives

	using System;
	using System.Collections.Generic;
	using System.ComponentModel;

	using ScrumSprintMonitor.Infrastructure;

	#endregion

	public class WorkItemObserver : IWorkItemObserver
	{
		private readonly List<WorkItemChangeDescriptor> changedWorkItemList = new List<WorkItemChangeDescriptor>();
		private readonly ILogger<WorkItemObserver> logger;
		private readonly IWorkItemStore workItemStore;
		private bool disposed;
		private bool initialRefreshTakenPlace;
		private List<IWorkItemAdapter> workItemCopiesList;

		public WorkItemObserver(IWorkItemStore workItemStore, ILogger<WorkItemObserver> logger)
		{
			if (workItemStore == null)
			{
				throw new ArgumentNullException("workItemStore");
			}

			this.workItemStore = workItemStore;
			this.logger = logger;

			SubscribeWorkItems();
		}

		private void SubscribeWorkItems()
		{
			var workItems = this.workItemStore.WorkItems;

			workItems.RaiseListChangedEvents = true;
			workItems.ListChanged += WorkItems_ListChanged;
			this.workItemStore.WorkItemsUpdated += workItemStore_WorkItemsUpdated;

			if (workItems.Count > 0)
			{
				this.initialRefreshTakenPlace = true;
			}

			this.workItemCopiesList = new List<IWorkItemAdapter>(workItems.Count);
			foreach (var workItem in workItems)
			{
				var workItemCopy = GetWorkItemCopy(workItem);
				this.workItemCopiesList.Add(workItemCopy);
			}
		}

		private static WritableWorkItem GetWorkItemCopy(IWorkItemAdapter workItem)
		{
			return new WritableWorkItem(workItem);
		}

		private void WorkItems_ListChanged(object sender, ListChangedEventArgs e)
		{
			try
			{
				switch (e.ListChangedType)
				{
					case ListChangedType.Reset:
						this.workItemCopiesList = new List<IWorkItemAdapter>(this.workItemStore.WorkItems.Count);

						foreach (var workItem in this.workItemStore.WorkItems)
						{
							var workItemCopy = GetWorkItemCopy(workItem);
							this.workItemCopiesList.Add(workItemCopy);
						}

						break;
					case ListChangedType.ItemAdded:
					{
						var workItem = this.workItemStore.WorkItems[e.NewIndex];
						var workItemCopy = GetWorkItemCopy(workItem);

						var changeDescriptor = new WorkItemChangeDescriptor
						                       	{
						                       		ChangeType = WorkItemChangeDescriptor.Operation.Added,
						                       		NewWorkItem = workItemCopy
						                       	};
						this.changedWorkItemList.Add(changeDescriptor);

						this.workItemCopiesList.Add(workItemCopy);

						break;
					}
					case ListChangedType.ItemDeleted:
					{
						var workItemCopy = this.workItemCopiesList[e.NewIndex];

						var changeDescriptor = new WorkItemChangeDescriptor
						                       	{
						                       		ChangeType = WorkItemChangeDescriptor.Operation.Removed,
						                       		OldWorkItem = workItemCopy
						                       	};
						this.changedWorkItemList.Add(changeDescriptor);

						this.workItemCopiesList.RemoveAt(e.NewIndex);

						break;
					}
					case ListChangedType.ItemChanged:
					{
						var workItemCopy = this.workItemCopiesList[e.NewIndex];
						var changeDescriptor =
							this.changedWorkItemList.Find(x => x.ChangeType == WorkItemChangeDescriptor.Operation.Changed && x.OldWorkItem.Id == workItemCopy.Id);

						if (changeDescriptor == null)
						{
							changeDescriptor = new WorkItemChangeDescriptor
							                   	{
							                   		ChangeType = WorkItemChangeDescriptor.Operation.Changed,
							                   		OldWorkItem = workItemCopy
							                   	};
							this.changedWorkItemList.Add(changeDescriptor);
						}

						switch (e.PropertyDescriptor.DisplayName)
						{
							case "ChangedBy":
								changeDescriptor.ChangedFields |= WorkItemChangeDescriptor.Field.ChangedBy;
								break;
							case "ChangedDate":
								changeDescriptor.ChangedFields |= WorkItemChangeDescriptor.Field.ChangedDate;
								break;
							case "AssignedTo":
								changeDescriptor.ChangedFields |= WorkItemChangeDescriptor.Field.AssignedTo;
								break;
							case "State":
								changeDescriptor.ChangedFields |= WorkItemChangeDescriptor.Field.State;
								break;
							case "Title":
								changeDescriptor.ChangedFields |= WorkItemChangeDescriptor.Field.Title;
								break;
							case "Type":
								changeDescriptor.ChangedFields |= WorkItemChangeDescriptor.Field.Type;
								break;
							case "DoneWork":
								changeDescriptor.ChangedFields |= WorkItemChangeDescriptor.Field.DoneWork;
								break;
							case "RemainingWork":
								changeDescriptor.ChangedFields |= WorkItemChangeDescriptor.Field.RemainingWork;
								break;
						}

						workItemCopy = GetWorkItemCopy(this.workItemStore.WorkItems[e.NewIndex]);
						changeDescriptor.NewWorkItem = workItemCopy;
						this.workItemCopiesList[e.NewIndex] = workItemCopy;

						break;
					}
					case ListChangedType.ItemMoved:
					{
						var workItemCopy = this.workItemCopiesList[e.OldIndex];
						this.workItemCopiesList.RemoveAt(e.OldIndex);
						this.workItemCopiesList.Insert(e.NewIndex, workItemCopy);
						break;
					}
					case ListChangedType.PropertyDescriptorAdded:
					case ListChangedType.PropertyDescriptorDeleted:
					case ListChangedType.PropertyDescriptorChanged:
						break;
					default:
						throw new ArgumentOutOfRangeException(string.Format("{0} is an unhandled ListChangeType.", e.ListChangedType));
				}
			}
			catch (Exception exception)
			{
				this.logger.Error(exception, "Found exception while processing work item change. e.ListChangedType = {0}, e.OldIndex = {1}, e.NewIndex={2}.",
				                  e.ListChangedType, e.OldIndex, e.NewIndex);

				throw;
			}
		}

		private void workItemStore_WorkItemsUpdated(object sender, EventArgs e)
		{
			if (!this.initialRefreshTakenPlace)
			{
				this.initialRefreshTakenPlace = true;
				return;
			}

			if (this.changedWorkItemList.Count != 0)
			{
				if (WorkItemsUpdated != null)
				{
					WorkItemsUpdated(this, new WorkItemsUpdatedEventArgs(this.changedWorkItemList));
				}
			}

			this.changedWorkItemList.Clear();
		}

		#region Implementation of IWorkItemObserver

		public IWorkItemStore WorkItemStore
		{
			get { return this.workItemStore; }
		}

		public event EventHandler<WorkItemsUpdatedEventArgs> WorkItemsUpdated;

		#endregion

		#region Implementation of IDisposable

		public void Dispose()
		{
			if (!this.disposed)
			{
				this.disposed = true;

				if (this.workItemStore != null)
				{
					this.workItemStore.WorkItems.ListChanged -= WorkItems_ListChanged;
					this.workItemStore.WorkItemsUpdated -= workItemStore_WorkItemsUpdated;
				}
			}
		}

		#endregion
	}
}